package lode

import (
	"encoding/json"
	"fmt"
	"github.com/Unixeno/islands/conf"
	"io/ioutil"
	"os"
	"path/filepath"
	"runtime"
	"time"
)

// 导入Docker镜像，镜像应为使用docker save方式导出的镜像，必须包含层信息
//
// 返回值：
// 		ImageId string：若导入成功，则返回一个本地的镜像id，若失败，则返回空id
//		error
func ImportDockerImage(path string, name string) (string, error) {
	if imageIsExist(name) {
		logger.Error("The image is exist")
		return "", fmt.Errorf("The image is exist")
	}
	return dockerImport(path, name)
}

func dockerImport(path string, name string) (string, error) {
	// if file not exist
	if !exists(path) {
		logger.Errorf("can't find the file : %s", path)
		return "", fmt.Errorf("can't find the file : %s", path)
	}

	logger.WithField("image", path).Info("start importing docker image")
	tmpDir, err := ioutil.TempDir(conf.GetString("Temp"), "")
	if err != nil {
		logger.Error("failed to create temporary folder")
		return "", nil
	}
	defer func() {
		if os.RemoveAll(tmpDir) != nil {
			logger.WithField("path", tmpDir).Error("failed to remove temporary folder")
		}
	}()

	// 把docker镜像解压到临时目录然后进行解析
	logger.Debug("untar docker image to " + tmpDir)
	if err := unTar(path, tmpDir, "tar"); err != nil {
		logger.WithField("error", err).Error("untar failed")
	} else {
		logger.Debug("untar success")
	}

	manifest, err := parseDockerManifest(filepath.Join(tmpDir, "manifest.json"))
	if err != nil {
		return "", err
	}
	// 解析镜像信息
	info, err := parseDockerConfig(filepath.Join(tmpDir, manifest.Config))
	if err != nil {
		return "", err
	}
	if info.Architecture != runtime.GOARCH {
		return "", nil
	}
	if info.Os != "linux" {
		return "", fmt.Errorf("invalid os, only support linux currently")
	}

	// 生成镜像的配置文件
	r := info.toBaseImage()
	r.Created = time.Now()

	// 镜像的层放入layer仓库，同时构建rootfs记录
	logger.Debugf("the image has %d layers", len(manifest.Layers))
	for _, layer := range manifest.Layers {
		logger.Debug("import layer " + layer)
		layerId, err := lodeDB.layers.storeTar(filepath.Join(tmpDir, layer), "tar")
		if err != nil {
			logger.Error("failed to import docker image")
		}
		r.AppendLayer(layerId)
	}

	logger.Debug("store the image to repository...")
	id, err := lodeDB.images.store(r, name)
	if err != nil {
		logger.WithError(err).Error("failed to import docker image")
		return "", err
	} else {
		logger.WithField("ID", id).Debug("import docker image success")
		return id, nil
	}
}

type dockerManifest struct {
	Config   string
	RepoTags []string
	Layers   []string
}

func parseDockerManifest(filename string) (dockerManifest, error) {
	logger.WithField("file", filename).Debug("start parsing manifest")
	var manifest []dockerManifest
	if data, err := ioutil.ReadFile(filename); err != nil {
		logger.WithField("file", filename).Error("failed to open the file for parsing")
		return dockerManifest{}, err
	} else {
		if err = json.Unmarshal(data, &manifest); err != nil {
			logger.WithError(err).Error("an error occurs while parsing the docker manifest")
			return dockerManifest{}, err
		} else {
			if len(manifest) == 1 {
				return manifest[0], nil
			}
			return dockerManifest{}, fmt.Errorf("invalid docker manifest format")
		}
	}
}

type dockerConfigV1 BaseImageConfig

// docker config是镜像的配置信息，
type dockerImageV1 struct {
	Architecture string
	Os           string
	Config       dockerConfigV1
	Created      time.Time
}

func parseDockerConfig(filename string) (dockerImageV1, error) {
	logger.WithField("file", filename).Debug("start parsing docker config")
	if data, err := ioutil.ReadFile(filename); err != nil {
		logger.WithField("file", filename).Error("failed to open the file for parsing")
		return dockerImageV1{}, err
	} else {
		config := dockerImageV1{}
		if err = json.Unmarshal(data, &config); err != nil {
			logger.WithError(err).Error("an error occurs while parsing the docker config")
			return dockerImageV1{}, err
		}
		return config, nil
	}
}

func (src dockerImageV1) toBaseImage() BaseImage {
	return BaseImage{
		Created:      src.Created,
		Architecture: src.Architecture,
		Os:           src.Os,
		Config:       BaseImageConfig(src.Config),
		Rootfs:       nil,
	}
}

type LayerInfo struct {
	MediaType string
	Digest    string
	Size      int
}

func ImportImageWithNeed(imageName string, configPath string, layers []LayerInfo) ([]LayerInfo, string, error) {

	resLayers := make([]LayerInfo, 0)

	for _, layer := range layers {
		digest := trimDigestPrefix(layer.Digest, "sha256:")
		_, ok := lodeDB.layers.syncDB.Load(digest)
		if !ok {
			resLayers = append(resLayers, LayerInfo{
				MediaType: layer.MediaType,
				Digest:    layer.Digest,
				Size:      layer.Size,
			})
		}
	}

	if len(resLayers) == 0 {
		info, err := parseDockerConfig(configPath)
		if err != nil {
			return resLayers, "", err
		}
		if info.Architecture != runtime.GOARCH {
			return resLayers, "", fmt.Errorf("invalid Architecture")
		}
		if info.Os != "linux" {
			return resLayers, "", fmt.Errorf("invalid os, only support linux currently")
		}

		r := info.toBaseImage()
		r.Created = time.Now()

		// 导入 layer
		for _, layer := range layers {
			layerId := trimDigestPrefix(layer.Digest, "sha256:")
			r.AppendLayer(layerId)
		}

		logger.Debug("store the image to repository...")
		id, err := lodeDB.images.store(r, imageName)
		if err != nil {
			logger.WithError(err).Error("failed to import docker image")
			return resLayers, "", err
		} else {
			logger.WithField("ID", id).Debug("import docker image success")
			return resLayers, id, nil
		}
	}

	return resLayers, "", nil
}

func ImportLayers(layersDir string, layers []LayerInfo) error {

	for _, layer := range layers {
		suffix, fileType := getTarFileSuffixAndType(layer.MediaType)
		layerId := trimDigestPrefix(layer.Digest, "sha256:")
		logger.Debug("import layer " + filepath.Join(layersDir, layerId+"."+suffix))
		_, err := lodeDB.layers.storeTar(filepath.Join(layersDir, layerId+"."+suffix), fileType)
		if err != nil {
			logger.WithError(err).Error("import layers failed")
			return err
		}
	}

	return nil
}
